package com.fengxing.ams.pub.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.params.ConnRouteParams;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A utility class for accessing a url via http protocol.
 */
public class HttpClientUtils2 {
	private Logger logger = LoggerFactory.getLogger(HttpClientUtils2.class);
	/**
     * The default configuration file.
     */
    private static final String HTTP_CONFIG_FILE = "httpconfig.properties";
    
    /**
     * The singleton instance of HttpClientUtils2.
     */
	private static final HttpClientUtils2 instance = new HttpClientUtils2();
	
    /**
     * Return the singleton instance of HttpClientUtils2.
     */
	public static HttpClientUtils2 getInstance() {
	    return instance;
	}
	
	/**
	 * Create and return a new instance of HttpClientUtils2 using the default configurations.
	 */
	public static HttpClientUtils2 newInstance() {
	    return new HttpClientUtils2();
	}
	
	/**
	 * Create and return a new instance of HttpClientUtils2 using the no validataion configurations.
	 */
	public static HttpClientUtils2 newNoValidationTrustInstance() {
		HttpClientUtils2 noValidationTrustInstance = new HttpClientUtils2();
		noValidationTrustInstance.setNoValidationTrust();
		return noValidationTrustInstance;
	}
	
    /**
     * A thread-safe client connection manager.
     */
    private ThreadSafeClientConnManager connectionManager;
    /**
     * A global http parameter used to create HttpClient instances.
     */
    private HttpParams httpParams = new BasicHttpParams();
    /**
     * A global default http client.
     */
    private HttpClient httpClient;
    
    /**
     * add client ip to parameter
     */
    private boolean addClientIP = false;
    
    private String defaultCharset = "UTF-8";
    
    /**
     * The IP of this current server.
     */
    private static final String serverIP = MiscUtils.getServerIP();
    
    private HttpClientUtils2() {
        ConfigurationReader confReader = new ConfigurationReader(HTTP_CONFIG_FILE);

        HttpConnectionParams.setConnectionTimeout(httpParams, confReader.getInt("http.connectionTimeout", 5000));
        HttpConnectionParams.setSoTimeout(httpParams, confReader.getInt("http.soTimeout", 300000));
        
        // set proxy parameters if configured.
        if (confReader.getBoolean("http.proxy.set", false)
                && StringUtils.isNotBlank(confReader.get("http.proxy.hostname"))) {
            HttpHost proxy = new HttpHost(confReader.get("http.proxy.hostname"), confReader.getInt("http.proxy.port", 80), "http");
            ConnRouteParams.setDefaultProxy(httpParams, proxy);
            ConnRouteParams.setDefaultProxy(new BasicHttpParams(), proxy);
        }
        
        connectionManager = new ThreadSafeClientConnManager();
        connectionManager.setMaxTotal(confReader.getInt("http.maxTotalConnections", 100));
        connectionManager.setDefaultMaxPerRoute(confReader.getInt("http.maxConnectionsPerRoute", 50));
        httpClient = new DefaultHttpClient(connectionManager, httpParams);
    }
    
    /**
     * Reset connection timeout and socket timeout for new http connections.
     * @param timeout the timeout value in millisecond.
     */
    public void setTimeout(int timeout) {
        setTimeout(timeout, timeout);
    }
    
    /**
     * Reset connection timeout and socket timeout for new http connections.
     * @param conTimeout the connection timeout value in millisecond.
     * @param soTimeout the socket timeout value in millisecond.
     */
    public void setTimeout(int conTimeout, int soTimeout) {
        int oldConnTimeout = HttpConnectionParams.getConnectionTimeout(httpParams);
        int oldSoTimeout = HttpConnectionParams.getSoTimeout(httpParams);
        
        if (oldConnTimeout != conTimeout || oldSoTimeout != soTimeout) {
            HttpConnectionParams.setConnectionTimeout(httpParams, conTimeout);
            HttpConnectionParams.setSoTimeout(httpParams, soTimeout);
            
            httpClient = new DefaultHttpClient(connectionManager, httpParams);
        }
    }
    
    /**
     * Set no validataion trust
     * 
     */
    public void setNoValidationTrust() {
		try {
			SSLContext sslContext = SSLContext.getInstance("TLS");
			sslContext.init(null,new TrustManager[] { new NoValidataionTrustManager() }, null);
			SSLSocketFactory ssf = new SSLSocketFactory(sslContext);
			httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, ssf));
		} catch (NoSuchAlgorithmException e) {
			throw new HttpClientException(" Set trust error, " + e.getMessage(), e);
		} catch (KeyManagementException e) {
			throw new HttpClientException(" Set trust error, " + e.getMessage(), e);
		}
    }
    
    /**
     * Return a new httpClient using default configuration parameters.
     * @return A new HttpClient object.
     */
    public HttpClient getHttpClient() {
        return httpClient;
    }
    
    /**
     * add client ip parameter
     * 
     * @param addClientIP
     */
	public void setAddClientIP(boolean addClientIP) {
		this.addClientIP = addClientIP;
	}
	
	/**
	 * @param defaultCharset
	 */
	public void setDefaultCharset(String defaultCharset) {
		this.defaultCharset = defaultCharset;
	}

	/**
     * Send a http 'get' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url. 
     */
    public TecHttpResponse get(String url) {
        return get(url, true);
    }

	/**
     * Send a http 'get' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url. 
     */
    public TecHttpResponse get(String url, boolean needEncodeUrl) {
        return sendGet(getHttpClient(), url, needEncodeUrl, null);
    }
    
    /**
     * Send a http 'get' request to given url and return the response content.
     * If the response status is not OK(200), HttpClientException would be thrown.
     * @param url the url that the request will be sent to.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url. 
     */
    public TecHttpResponse getStrictly(String url) {
        return getStrictly(url, true);
    }
    
    /**
     * Send a http 'get' request to given url and return the response content.
     * If the response status is not OK(200), HttpClientException would be thrown.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url. 
     */
    public TecHttpResponse getStrictly(String url, boolean needEncodeUrl) {
        return sendGetStrictly(getHttpClient(), url, needEncodeUrl, null);
    }
    
    /**
     * Send a http 'get' request to given url and return the response content.
     * it uses the apache http single thread model for the connection management. 
     * @param url the url that will send request to.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return  
     */
    public TecHttpResponse getWithSingleThread(String url) {
        return getWithSingleThread(url, true);
    }
    
    /**
     * Send a http 'get' request to given url and return the response content.
     * it uses the apache http single thread model for the connection management. 
     * @param url the url that will send request to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return  
     */
    public TecHttpResponse getWithSingleThread(String url, boolean needEncodeUrl) {
        return sendGet(new DefaultHttpClient(), url, needEncodeUrl, null);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse get(String url, Map<String, String> paramsMap) {
        return get(url, true, paramsMap);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse get(String url, boolean needEncodeUrl, Map<String, String> paramsMap) {
        return sendGet(getHttpClient(), buildParameterUrl(url, paramsMap), needEncodeUrl, null);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse get2(String url, Map<String, List<String>> paramsMap) {
        return get2(url, true, paramsMap);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse get2(String url, boolean needEncodeUrl, Map<String, List<String>> paramsMap) {
        return sendGet(getHttpClient(), buildParameterUrl2(url, paramsMap), needEncodeUrl, null);
    }
    
    /**
     * Send a http 'get' request to given url and return the response content.
     * If the response status is not OK(200), HttpClientException would be thrown.
     * @param url the url that the request will be sent to.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url. 
     */
    public TecHttpResponse getStrictly(String url, Map<String, String> paramsMap) {
        return getStrictly(url, true, paramsMap);
    }
    
    /**
     * Send a http 'get' request to given url and return the response content.
     * If the response status is not OK(200), HttpClientException would be thrown.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url. 
     */
    public TecHttpResponse getStrictly(String url, boolean needEncodeUrl, Map<String, String> paramsMap) {
        return sendGetStrictly(getHttpClient(), buildParameterUrl(url, paramsMap), needEncodeUrl, null);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse get2Strictly(String url, Map<String, List<String>> paramsMap) {
        return get2Strictly(url, true, paramsMap);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse get2Strictly(String url, boolean needEncodeUrl, Map<String, List<String>> paramsMap) {
        return sendGetStrictly(getHttpClient(), buildParameterUrl2(url, paramsMap), needEncodeUrl, null);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse post(String url, Map<String, String> paramsMap) {
        return post(url, true, paramsMap);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse post(String url, boolean needEncodeUrl, Map<String, String> paramsMap) {
        if (MapUtils.isEmpty(paramsMap)) {
            return sendGet(getHttpClient(), url, needEncodeUrl, null);
        } else {
            return sendPost(getHttpClient(), url, needEncodeUrl, paramsMap, null, null);
        }
    }
    
    private String buildParameterUrl(String url, Map<String, String> paramsMap) {
        if (MapUtils.isNotEmpty(paramsMap)) {
            StringBuffer paraBuilder = new StringBuffer(512);
            for (String name : paramsMap.keySet()) {
                paraBuilder.append("&");
                paraBuilder.append(name);
                paraBuilder.append("=");
                paraBuilder.append(paramsMap.get(name));
            }
            paraBuilder.setCharAt(0, '?');
            if (paraBuilder.length() > 1) {
                url = url + paraBuilder.toString();
            }
        }
        return url;
    }
    
    private String buildParameterUrl2(String url, Map<String, List<String>> paramsMap) {
        if (MapUtils.isNotEmpty(paramsMap)) {
            StringBuffer paraBuilder = new StringBuffer(512);
            for (String name : paramsMap.keySet()) {
                for (String value : paramsMap.get(name)) {
                    paraBuilder.append("&");
                    paraBuilder.append(name);
                    paraBuilder.append("=");
                    paraBuilder.append(value);
                }
            }
            paraBuilder.setCharAt(0, '?');
            if (paraBuilder.length() > 1) {
                url = url + paraBuilder.toString();
            }
        }
        return url;
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse post2(String url, Map<String, List<String>> paramsMap) {
        return post2(url, true, paramsMap);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url.
     */
    public TecHttpResponse post2(String url, boolean needEncodeUrl, Map<String, List<String>> paramsMap) {
        if (MapUtils.isEmpty(paramsMap)) {
            return sendGet(getHttpClient(), url, needEncodeUrl, null);
        } else {
            return sendPost2(getHttpClient(), url, needEncodeUrl, paramsMap, null, null);
        }
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url.
     */
    public TecHttpResponse postStrictly(String url, Map<String, String> paramsMap) {
        return postStrictly(url, true, paramsMap);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url.
     */
    public TecHttpResponse postStrictly(String url, boolean needEncodeUrl, Map<String, String> paramsMap) {
        if (MapUtils.isEmpty(paramsMap)) {
            return sendGetStrictly(getHttpClient(), url, needEncodeUrl, null);
        } else {
            return sendPostStrictly(getHttpClient(), url, needEncodeUrl, paramsMap, null, null);
        }
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url.
     */
    public TecHttpResponse post2Strictly(String url, Map<String, List<String>> paramsMap) {
        return post2Strictly(url, true, paramsMap);
    }
    
    /**
     * Send a http 'post' request to given url and return the response content.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap the parameters will be sent to given url.
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return the response content got from the specified url.
     */
    public TecHttpResponse post2Strictly(String url, boolean needEncodeUrl, Map<String, List<String>> paramsMap) {
        if (MapUtils.isEmpty(paramsMap)) {
            return sendGetStrictly(getHttpClient(), url, needEncodeUrl, null);
        } else {
            return sendPost2Strictly(getHttpClient(), url, needEncodeUrl, paramsMap, null, null);
        }
    }
    
    /**
     * @param url
     * @param requestContent
     * @param headerMap
     * @return
     */
    public TecHttpResponse request(String url, String requestContent, Map<String, String> headerMap) {
        return request(url, true, requestContent, headerMap);
    }
    
    /**
     * @param url
     * @param requestContent
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param headerMap
     * @return
     */
    public TecHttpResponse request(String url, boolean needEncodeUrl, String requestContent, Map<String, String> headerMap) {
        if (StringUtils.isBlank(requestContent)) {
            return sendGet(getHttpClient(), url, needEncodeUrl, headerMap);
        } else {
            return sendPost(getHttpClient(), url, needEncodeUrl, null, requestContent, headerMap);
        }
    }
    
    /**
     * Send a http 'get' request to given url via the given httpClient object,
     * and return the response content.
     * @param httpClient the apache httpClient object that manages the request.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return the response content got from the specified url. 
     */
    private TecHttpResponse sendGet(HttpClient httpClient, String url, boolean needEncodeUrl, Map<String, String> headerMap) {
        try {
            return sendGetStrictly(httpClient, url, needEncodeUrl, headerMap);
        } catch (HttpClientException e) {
            // caused by invalid response status, return an empty result.
            if (e.getStatusLine() != null) {
            	logger.error("Send get request to " + url + " failed", e);
            	return new TecHttpResponse("", new Header[] {});
            }
            throw e;
        }
    }
    
    /**
     * Send a http 'get' request to given url via the given httpClient object,
     * and return the response content.
     * @param httpClient the apache httpClient object that manages the request.
     * @param url the url that the request will be sent to.
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @return the response content got from the specified url. 
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     */
    private TecHttpResponse sendGetStrictly(HttpClient httpClient, String url, boolean needEncodeUrl, Map<String, String> headerMap) {
    	HttpGet httpGet = null;
    	// add client ip param
    	if (addClientIP) {
    		url = addClientIPParameter(url);
    	}
    	
    	if (needEncodeUrl) {
    		try {
    		    // Use uri to construct the url again for encoding.
    		    URL urlObj = new URL(url);
    		    URI uri = new URI(urlObj.getProtocol(), null, urlObj.getHost(), urlObj.getPort(),
    		            urlObj.getPath(), urlObj.getQuery(), null);		
    			httpGet = new HttpGet(uri);
    		} catch (MalformedURLException e) {
    			logger.error("Invalid url" + url, e);
    		} catch (URISyntaxException e) {
    		    logger.error("Invalid url" + url, e);
    		}
    	}
    	if (httpGet == null) {
    	    httpGet =  new HttpGet(url);
    	}
        if (MapUtils.isNotEmpty(headerMap)) {
            List<Header> headers = new ArrayList<Header>();
            for (String key: headerMap.keySet()) {
                headers.add(new BasicHeader(key, headerMap.get(key)));
            }
            httpGet.setHeaders(headers.toArray(new Header[headers.size()]));
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Sending get to " + url);
        }
        
        return internalSendRequest(httpClient, httpGet);
    }
    
    private String addClientIPParameter(String url) {
    	return addUrlParameter(url, "ClientIP", serverIP);
	}

	private String addUrlParameter(String url, String key, String value) {
	    return url + (url.contains("?") ? "&" : "?") + key + "=" + value;
	}

	/**
     * get response by post method
     * @param httpClient
     * @param url
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return
     */
    private TecHttpResponse sendPost(HttpClient httpClient, String url, boolean needEncodeUrl,
            Map<String, String> paramsMap, String requestContent, Map<String, String> headerMap) {
        try {
            return sendPostStrictly(httpClient, url, needEncodeUrl, paramsMap, requestContent, headerMap);
        } catch (HttpClientException e) {
            // caused by invalid response status, return an empty result.
            if (e.getStatusLine() != null) {
            	logger.error("Send post request to " + url + " failed", e);
            	return new TecHttpResponse("", new Header[] {});
            }
            throw e;
        }
    }
    
    /**
     * get response by post method
     * @param httpClient
     * @param url
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap
     * @throws HttpClientException thrown only when the IO exception occurs.
     * @return
     */
    private TecHttpResponse sendPost2(HttpClient httpClient, String url, boolean needEncodeUrl,
            Map<String, List<String>> paramsMap, String requestContent, Map<String, String> headerMap) {
        try {
            return sendPost2Strictly(httpClient, url, needEncodeUrl, paramsMap, requestContent, headerMap);
        } catch (HttpClientException e) {
            // caused by invalid response status, return an empty result.
            if (e.getStatusLine() != null) {
            	logger.error("Send post request to " + url + " failed", e);
                return new TecHttpResponse("", new Header[] {});
            }
            throw e;
        }
    }
    
    /**
     * get response by post method
     * @param httpClient
     * @param url
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return
     */
    private TecHttpResponse sendPostStrictly(HttpClient httpClient, String url, boolean needEncodeUrl,
            Map<String, String> paramsMap, String requestContent, Map<String, String> headerMap) {
        List<NameValuePair> nvps = null;
        if (MapUtils.isNotEmpty(paramsMap)) {
            nvps = new ArrayList<NameValuePair>();
            for (String name : paramsMap.keySet()) {
                nvps.add(new BasicNameValuePair(name, paramsMap.get(name)));
            }
        }
        return sendPostStrictly(httpClient, url, needEncodeUrl, nvps, requestContent, headerMap);
    }
    
    /**
     * get response by post method
     * @param httpClient
     * @param url
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param paramsMap
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return
     */
    private TecHttpResponse sendPost2Strictly(HttpClient httpClient, String url, boolean needEncodeUrl,
            Map<String, List<String>> paramsMap, String requestContent, Map<String, String> headerMap) {
        List<NameValuePair> nvps = null;
        if (MapUtils.isNotEmpty(paramsMap)) {
            nvps = new ArrayList<NameValuePair>();
            for (String name : paramsMap.keySet()) {
                for (String value : paramsMap.get(name)) {
                    nvps.add(new BasicNameValuePair(name, value));
                }
            }
        }
        return sendPostStrictly(httpClient, url, needEncodeUrl, nvps, requestContent, headerMap);
    }
    
    /**
     * get response by post method
     * @param httpClient
     * @param url
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @param nvps
     * @throws HttpClientException thrown when the IO exception occurs or the response status is not OK(200).
     * @return
     */
    private TecHttpResponse sendPostStrictly(HttpClient httpClient, String url, boolean needEncodeUrl,
            List<NameValuePair> nvps, String requestContent, Map<String, String> headerMap) {
    	HttpPost httpPost = null;
    	// add clientIp
    	url = addClientIPParameter(url);
    	if (needEncodeUrl) {
    		try {
    		    URL urlObj = new URL(url);
    		    URI uri = new URI(urlObj.getProtocol(), null, urlObj.getHost(), urlObj.getPort(),
    		            urlObj.getPath(), urlObj.getQuery(), null);
    			httpPost = new HttpPost(uri);
    		} catch (MalformedURLException e) {
    		    logger.error("Invalid url" + url, e);
    		} catch (URISyntaxException e) {
    		    logger.error("Invalid url" + url, e);
    		}
    	}
    	if (httpPost == null) {
    	    httpPost = new HttpPost(url);
    	}
        if (logger.isDebugEnabled()) {
            StringBuilder urlBuilder = new StringBuilder(url);
            if (CollectionUtils.isNotEmpty(nvps)) {
                if (!url.contains("?")) {
                    urlBuilder.append("?");
                }
                boolean existParameters = !urlBuilder.toString().endsWith("?");
                for (NameValuePair nvp : nvps) {
                    if (existParameters) {
                        urlBuilder.append("&");
                    } else {
                        existParameters = true;
                    }
                    urlBuilder.append(nvp.getName()).append("=").append(nvp.getValue());
                }
            }
            logger.debug("Sending post to " + url + ", the full url with parameters: " + urlBuilder);
        }
        // add params
        if (CollectionUtils.isNotEmpty(nvps)) {
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
            } catch (UnsupportedEncodingException e) {
                throw new HttpClientException("URL encoding error", e);
            }
        } else {
            httpPost.setEntity(new StringEntity(requestContent, HTTP.UTF_8));
        }
        
        // add http header
        if (MapUtils.isNotEmpty(headerMap)) {
            List<Header> headers = new ArrayList<Header>();
            for (String name : headerMap.keySet()) {
                Header header = new BasicHeader(name, headerMap.get(name));
                headers.add(header);
            }
            httpPost.setHeaders(headers.toArray(new Header[headers.size()]));
        }
        
        return internalSendRequest(httpClient, httpPost);
    }
    
    private TecHttpResponse internalSendRequest(final HttpClient httpClient, final HttpUriRequest request) {
        HttpEntity entity = null;
        long start = 0L;
        try {
            if (logger.isDebugEnabled()) {
                int oldConnTimeout = HttpConnectionParams.getConnectionTimeout(httpParams);
                int oldSoTimeout = HttpConnectionParams.getSoTimeout(httpParams);
                logger.debug("oldConnTimeout = " + oldConnTimeout + ", oldSoTimeout = " + oldSoTimeout);
            }
            
        	request.addHeader("Accept-Encoding", "gzip");
        	start = System.currentTimeMillis();
            if (logger.isDebugEnabled()) {
                logger.debug("starting http request...");
            }
        	HttpResponse response = httpClient.execute(request);
            if (logger.isDebugEnabled()) {
                logger.debug("http request executed, took  " + (System.currentTimeMillis() - start) + "ms");
            }
            entity = response.getEntity();
            if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) {
                Header[] headers = response.getAllHeaders();
                String server = null;
                if (ArrayUtils.isNotEmpty(headers)) {
                    for (Header header : headers) {
                        if (header.getName().equals("ServerInfo")
                                || header.getName().equals("Server")) { // Server:SWS-157.241.136.240-A
                            server = header.getValue();
                        }
                    }
                }
                throw new HttpClientException(request.getMethod() + " request to " + request.getURI()
                        + " (internal server:" + server + ") failed with status code "
                        + response.getStatusLine().getStatusCode() + " returned",
                        response.getStatusLine(), (entity == null ? null : EntityUtils.toString(entity)));
            }
            Header header = response.getFirstHeader("Content-Encoding");
            boolean isCompression = (header != null && header.getValue().equals("gzip"))? true : false;
            String result = EntityUtils.toString(isCompression? new GzipDecompressingEntity(entity): entity, defaultCharset);
            Header[] headers = response.getAllHeaders();
            return new TecHttpResponse(result, headers);
        } catch (ClientProtocolException e) {
            if (request != null) {
                request.abort();
            }
            throw new HttpClientException(request.getMethod() + " URL content HTTP error, " + e.getMessage(), e);
        } catch (HttpHostConnectException e) {
            if (request != null) {
                request.abort();
            }
            if (logger.isDebugEnabled()) {
                logger.debug("got time out for " + (System.currentTimeMillis() - start) + " ms and input"
                        + request.getParams().getParameter("input") + "-" + request.getURI().getHost());
            }
            throw new HttpClientException(request.getMethod() + " URL content IO error, " + e.getMessage(), e);
        } catch (IOException e) {
            if (request != null) {
                request.abort();
            }
            if (logger.isDebugEnabled()) {
                logger.debug("got io exception for " + (System.currentTimeMillis() - start) + " ms and input"
                        + request.getParams().getParameter("input") + "-" +  request.getURI().getHost());
            }
            throw new HttpClientException(request.getMethod() + " URL content IO error, " + e.getMessage(), e);
        } finally {
            // call this method to consume any available content on the stream. HttpClient
            // will automatically release the underlying connection back to the connection manager as soon as it
            // detects that the end of the content stream has been reached.
            try {
                EntityUtils.consume(entity);
            } catch (IOException e) {
            	logger.error("Close http connections failed", e);
            }
        }
    }
    
    /**
     * check url status
     * @param url
     * @return 
     */
    public int getUrlStatus(String url) {
        return getUrlStatus(url, true);
    }
    
    /**
     * check url status
     * @param url
     * @param needEncodeUrl indicates if needs to execute encoding on the given url.
     * @return 
     */
    public int getUrlStatus(String url, boolean needEncodeUrl) {
        if (!url.matches("http://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?")) {
            return 0;
        }
        
        int statusCode = 0;
        HttpGet httpGet = null;
        if (needEncodeUrl) {
    		try {
    		    URL urlObj = new URL(url);
    		    URI uri = new URI(urlObj.getProtocol(), null, urlObj.getHost(), urlObj.getPort(), urlObj.getPath(), urlObj.getQuery(), null);
    			httpGet = new HttpGet(uri);
    		} catch (MalformedURLException e) {
    		    logger.error("Invalid url" + url, e);
    		} catch (URISyntaxException e) {
    		    logger.error("Invalid url" + url, e);
    		}
        }
        if (httpGet == null) {
            httpGet = new HttpGet(url);
        }
        
        HttpEntity entity = null;
        try {
            HttpResponse response = getHttpClient().execute(httpGet);
            statusCode = response.getStatusLine().getStatusCode();
            entity = response.getEntity();
        } catch (ClientProtocolException e) {
            httpGet.abort();
        } catch (IOException e) {
            httpGet.abort();
        } finally {
            // call this method to consume any available content on the stream. HttpClient
            // will automatically release the underlying connection back to the connection manager as soon as it
            // detects that the end of the content stream has been reached.
            try {
                EntityUtils.consume(entity);
            } catch (IOException e) {
                logger.error("Close http connections failed", e);
            }
        }
        return statusCode;
    }
    
    /**
     * No validation trust
     *
     */
    class NoValidataionTrustManager implements X509TrustManager {
    	@Override
    	public void checkClientTrusted(X509Certificate[] chain, String authType)
    			throws CertificateException {
    	}

    	@Override
    	public void checkServerTrusted(X509Certificate[] chain, String authType)
    			throws CertificateException {
    	}

    	@Override
    	public X509Certificate[] getAcceptedIssuers() {
    		return null;
    	}
    }
}
